re render on state change with useEffect - reactjs

App won't re render different data on state change.
State does change in the dev tools but doesn't show on page.
Using button to filter.
export const StoriesPage = () => {
const [storyIds, setStoryIds] = useState([]);
const [storyUrl, setStoryUrl] = useState('top');
useEffect(() => {
let url = baseUrl;
storyUrl === 'top'
? (url += 'topstories.json')
: (url += 'newstories.json');
getStoryIds(url).then(data => setStoryIds(data));
}, [storyUrl]);
return (
<div>
<div>
<button onClick={() => setStoryUrl('new')}>New</button>
<button onClick={() => setStoryUrl('top')}>Top</button>
</div>
{storyIds.map((storyId, index) => (
<Story key={index} storyId={storyId} />
))}
</div>
);
};

Added a function that clears storyIds before setStoryUrl
const handleFilter = tag => {
setStoryIds([]);
setStoryUrl(tag);
};

Related

Changes don't render in React

I have array of items and searching items function, that returns another array. When I delete or edit item finded items changes don't render, but when search string has another value React render changes.
I know that useEffect can resolve this problem, but dont what to put in callback.
How can resolve this problem?
export const ToDoList = (props: PropsType) => {
const [searchQuery, setSearchQuery] = useState('')
const searchedItems = useMemo(() => {
return props.ToDoData.filter(item => item.text.includes(searchQuery))
},
[searchQuery])
return (
{props.ToDoData.length ?
<>
<input
...
onChange={e => setSearchQuery(e.target.value)}
/>
<ItemsList
...
items={
searchQuery ?
searchedItems :
props.ToDoData
}
/>
</> :
...
}
)
}
export const ItemsList = (props: PropsType) => {
const [editedText, setEditedText] = useState('')
const onDeleteItem = (id: number) => {
props.dispatch(deleteItem(id))
},
onEditItemMode = (id: number, text: string) => {
props.dispatch(setEditMode(true, id))
setEditedText(text)
},
onEditText = (id: number) => {
props.dispatch(setEditedTextInItem(id, editedText))
props.dispatch(setEditMode(false, id))
setEditedText('')
},
onToggleCompletedStatus = (id: number, status: string) => {
...
}
return (
{props.items.length ?
props.items.map((object) => (
<div
className="Item"
key={object.id}
>
{props.inEditMode.some((id: number) => id === object.id) ?
<>
<input
value={editedText}
onChange={e => { setEditedText(e.currentTarget.value) }}
/>
<button onClick={() => onEditText(object.id)}>
Change text
</button>
</> :
<>
<div className="Item__textBlock">
<input
type='checkbox'
onClick={() => { onToggleCompletedStatus(object.id, object.status)}}
/>
<span className={
object.status === 'completed' ?
'completed' :
'in process'
}>
{object.text}
</span>
</div>
<div className="Item__buttonBlock">
<button
className="Item__button"
disabled={props.inEditMode.length !== 0}
onClick={() => onEditItemMode(object.id, object.text)}
>
<img src={editImg} />
</button>
<button
className="Item__button"
onClick={() => { onDeleteItem(object.id) }}
>
<img src={removeImg} />
</button>
</div>
</>
}
</div>
)) :
...
}
)
}
// This code creates a list that is ONLY updated when searchQuery is updated
const searchedItems = useMemo(() => {
return props.ToDoData.filter(item => item.text.includes(searchQuery))
}, [searchQuery]);
// This code creates the list every time the component renders,
// so it will always be correct
const searchedItems = props.ToDoData.filter(item => item.text.includes(searchQuery))
// If you absolutely need to optimize the render of this component
// This code will update the list whenever the reference for ToDoData is updated as well
const searchedItems = useMemo(() => {
return props.ToDoData.filter(item => item.text.includes(searchQuery))
}, [searchQuery, props.ToDoData]);

how to avoid a component being re-render?

how to avoid re-render a child component , I've tested use memo hook it did not work with me can you please help and correct my code .
here is my code
Home Component :
function Home({todos, todoFun }) {
const [ increment ,setIncrement] = useState(1);
const [data , setData] = useState(['test']);
const v = 'some text';
const addValue = () =>{
setData( (data) => [...data, v ]);
}
useMemo(() => data , [data]);
return (
<div>
<h1>{increment}</h1>
<Todos todos ={data} todoFun={addValue}/>
<br />
<button onClick={() => setIncrement(increment => increment + 1)} style={{pading: "20px"}}>
Increment
</button>
</div>
)
}
and from Todos component :
function Index({todos, todoFun }) {
return (
<div>
{console.log('hello from child ')}
{todos.map((todo, index ) =>
{
return <p key={index}>{todo}</p>;
}
)}
<button onClick={todoFun}>Add Text</button>
</div>
)
}
export default memo(Index);
it keep displaying hello from child , when clicking on both buttons
that is how I solved the issue thanks to #Liam's comment , editing add value function :
const addValue = useCallback((e)=>{
setData( (data) => [...data, v ]);
}, [data])

How to show only the selected item in the modal

I am quite new to modals on react. I have 2 questions:
I am trying to show more details onclick the particular gif
what type of test can I write for the async/await response(I have never written a test before)
Further details:
react version: 18
async function handleSubmit(event) {
event.preventDefault();
await axios.get(`https://api.giphy.com/v1/gifs/search?q=${gifname}&api_key=3ZT8IGYuq0IQP1v19SAGm1RNkL5L5FUI`)
.then((response) => {
let resp = response.data.data
setgif(resp)
})
};
useEffect (() => {
handleSubmit()}, []
)
function GifList(props) {
const gif_use = props.gif;
const [modalIsOpen, setModalIsOpen] = useState(false);
return (
<>
<div className="img_div row">
{
gif_use.map((gif_use1, i) =>
<img className="col-lg-4" src={gif_use1.images.downsized.url}
alt='gif-result'
onClick={
() => setModalIsOpen(true)
}
key={i}/>
)}
</div>
<Modal isOpen={modalIsOpen} onRequestClose={
() => setModalIsOpen(false)
}>
{
gif_use.map((gif_use1, i) =>
<img className="col-lg-4" src={gif_use1.images.downsized.url}
alt='gif-result'
onClick={
() => setModalIsOpen(true)
}
key={i}/>
)}
<button onClick={
() => setModalIsOpen(false)
}>close</button>
</Modal>
</>
);
}

React - How to prevent parent re-render on prop change

I am making a calculator using react.
Every time I press a number button, the whole application re-renders, instead of the <Display />.
To prevent it, I tried 2 different approaches for App, But neither of them worked.
Here is the sandbox link.
Any help would be appreciated.
Put clickHandler inside of useCallback()
const App = () => {
const [screen, setScreen] = useState("0");
console.log("render");
const clickHandler = useCallback(
(val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
},
[screen]
);
return (
<div className="App">
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</div>
);
};
Put Display component inside of React.memo
const App = () => {
const [screen, setScreen] = useState("0");
console.log("render");
const clickHandler = (val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
};
const displayComponent = () => {
return (
<>
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</>
);
};
const MemoizedComponent = React.memo(displayComponent);
return (
<div className="App">
<MemoizedComponent />
</div>
);
};
And here's the ButtonList & Button component.
export const ButtonList = ({ clickHandler }) => {
const arr = [...Array.from(Array(10).keys()).reverse(), "AC"];
return (
<div className="buttons">
<div className="numbersWrapper">
{arr.map((item) => (
<Button
key={item}
clickHandler={clickHandler}
value={item.toString()}
/>
))}
</div>
</div>
);
};
export const Button = ({ value, clickHandler }) => {
return (
<button
name={value}
onClick={() => {
clickHandler(value); //where the clickEvent happens
}}
>
{value}
</button>
);
};
If you don't want a component re-render,You would have to define the click handler in another component that you would like to re-render.
So do it like this:
const App = () => {
console.log("render");
return (
<div className="App">
<childComponent />
</div>
);
};
export const childComponent = () => {
const [screen, setScreen] = useState("0");
const clickHandler = (val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
};
return (
<>
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</>
);
}
This way you prevent a particular component from re-rendering. But note that if you update a state or do anything from which causes re-renders from the parent component, It would equally re-render the child component.

How to render a component on Click on list item to show its detail?

I have a Dashboard component that is fetching all city data from an API and store it in the cities state.
Now I want that when a city name is clicked a new page opens having all the props of the clicked city.
function Dashboard() {
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
// Here I want to show a detail page of the clicked item //
// <DetailsPage city={e} />
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you like to show the details under the selected city you can keep it in your component state and render it conditionally:
function Dashboard() {
const [selectedCity, setSelectedCity] = useState(null);
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
setSelectedCity(e)
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
{selectedCity === city ? <DetailsPage city={city} /> : null}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you want to only show the selected city content (with probably a back button):
function Dashboard() {
const [selectedCity, setSelectedCity] = useState(null);
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
setSelectedCity(e)
}
if (selectedCity) {
return <DetailsPage city={e} onBack={() => setSelectedCity(null)} />
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you want a separate page with a different URL, it will be more complex than this.
You need to use a router like react-router.
const handleClick = (e) => {
history.push("/city", { id: e.id });
}
You have to read the data on both pages. So you may need to put your cities and the selected city values in a React Context so that you can use it on the details page. Alternatively, you can fetch the data on the parent component and move these states to it, so that you can pass the values to both pages.
If you fetch data on the Dashboard page, you should also handle the scenario in which a user refreshes the details page or enters the URL directly. You may need a different API to fetch a city by its ID. Alternatively, you can simply redirect to the Dashboard page if you are on the details page and you don't have the required data.

Resources