When you're updating a UI with data from the server, you often need to modify the JSON responses received on the frontend by merging some external properties to the array. These properties can be needed for mapping data to your state and using that state to create controlled components. For instance, if your server returns a list of users and you need to add a checkbox next to each user as a controlled component, you need to append an additional property to every element of the array that keeps track of the selected users.
This guide shows how you can simply loop over a JSON array and merge properties of array elements using JavaScript's advanced array methods such as map()
and filter()
.
The example demonstrated in this guide covers a simple use case in which your server returns a list of tasks to the frontend and you need to show a checkbox next to each task to mark it as completed or yet to be completed. A generic use case for this example would be an online portal where a manager generates tasks for the employees and the employee updates these tasks at the end of the day by marking them as complete or incomplete.
For the sake of demonstration, assume that all tasks are returned from the https://jsonplaceholder.typicode.com/todos endpoint. The above endpoint returns some dummy JSON containing a list of todos
that can be considered as tasks with reference to this example. You need a state
to keep track of this data and a function that makes a GET
request to fetch all the data using fetch
API. Consider the following code:
1import React,{useState,useEffect} from 'react';
2import './App.css';
3
4function App() {
5 const [tasks,setTasks]=useState([])
6 const getTasks=()=>{
7 fetch('https://jsonplaceholder.typicode.com/todos')
8 .then(response => response.json())
9 .then(json =>{
10 console.log(json)
11 }
12 useEffect(()=>{
13 getTasks();
14 },[])
15
16 return (
17 <div className="App">
18
19 </div>
20 );
21}
22
23export default App;
You need to call getTasks()
inside useEffect()
as shown above in order to make this API call the first time your component mounts on the DOM.
Inspecting the console, you can observe that the array of todos
already contains a property completed
. Also, the endpoint returns a large number of todos
, so filter out the array using the filter()
method by keeping only the first 10 todos for simplifying the data and remove the completed
property from each todo. The latter is done because the API your app consumes will not return this property from the server.
1 const getTasks=()=>{
2 fetch('https://jsonplaceholder.typicode.com/todos')
3 .then(response => response.json())
4 .then(json =>{
5 let allTasks=json;
6 allTasks=allTasks.filter((currentTask)=>{return currentTask.id<=10 && {id:currentTask.id,title:currentTask.title}})
7 console.log('taks: ',allTasks)
8
9 })
10 }
The completed
property is external to this data and must be kept on the frontend only. The next step is to merge this property with the array and set it false
for every element by default using the map()
method. You should also set your state
to allTasks
.
1 allTasks=allTasks.map((currentTask)=>{
2 return {...currentTask, completed:false}
3 })
4 setTasks(allTasks)
Using a component loop, iterate over the tasks
array and render them on the DOM with a controlled input checkbox next to each task.
1return (
2 <div className="App">
3 {
4 tasks && tasks.map(task=>{
5 return(
6 <p key={task.id}>{task.title} <input onChange={()=>handleChange(task.id)} type="checkbox" checked={task.completed}/></p>
7 )
8 })
9 }
10 </div>
11 );
You can leverage the id
returned from the API for every task as its key
. The checkbox is in line with your state's data, rendering it a controlled component. On selecting or de-selecting the checkbox you need to fire a function that checks if a task has been marked completed and modifies that property accordingly. Attach an onChange
event listener to the checkbox input and fire a function that takes the task's id
as a parameter.
Inside the handleChange()
method, loop through your tasks
and invert the completed
property for the task whose id
matches the id
passed.
1 const handleChange=(id)=>{
2 let temp=tasks;
3 temp=temp.map((t)=>{
4 if(t.id===id)
5 t.completed=!t.completed
6 return t;
7 })
8 setTasks(temp)
9 console.log(id)
10 }
Have a look at the entire code below .
1import React,{useState,useEffect} from 'react';
2import './App.css';
3
4function App() {
5 const [tasks,setTasks]=useState([])
6 const getTasks=()=>{
7 fetch('https://jsonplaceholder.typicode.com/todos')
8 .then(response => response.json())
9 .then(json =>{
10 let allTasks=json;
11 allTasks=allTasks.filter((currentTask)=>{return currentTask.id<=10 && {id:currentTask.id,title:currentTask.title}})
12 console.log('taks: ',allTasks)
13 allTasks=allTasks.map((currentTask)=>{return {...currentTask, completed:false}})
14 setTasks(allTasks)
15 })
16 }
17 useEffect(()=>{
18 getTasks();
19 },[])
20 useEffect(()=>{
21 console.log(tasks)
22 },[tasks])
23 const handleChange=(id)=>{
24 let temp=tasks;
25 temp=temp.map((t)=>{
26 if(t.id===id)
27 t.completed=!t.completed
28 return t;
29 })
30 setTasks(temp)
31 console.log(id)
32 }
33 return (
34 <div className="App">
35 {
36 tasks && tasks.length>0 && tasks.map(task=>{
37 return(
38 <p key={task.id}>{task.title} <input onChange={()=>handleChange(task.id)} type="checkbox" checked={task.completed}/></p>
39 )
40 })
41 }
42 </div>
43 );
44}
45
46export default App;
You can conveniently merge properties of array elements from a JSON response by manipulating them using JavaScript methods such as map()
and filter()
. Merging external properties is a good technique as it allows you to handle the data on the frontend the way you want to, but you must ensure that you send back the same structure of data to the server that your database expects.