I'm pulling in some data from a session that I need to be able to access before I can set the initial state for the path. I've tried a myriad of different ways. It's to show an active state of a link so the user knows where they are, this below works if the page is refreshed but not on initial load. Any help would be greatly apporeciated:
const StickyNav = () => {
const { sessionToken, sessionData } = useContext(SessionContext);
const stickyNavItems = sessionData && sessionToken ? stickyNavItemsByRole(sessionData.user) : null;
const [path, setPath] = useState(stickyNavItems ? stickyNavItems[0].defaultPath : "");
useEffect(() => {
// console.log("useEffect being hit with: ", stickyNavItems[0].defaultPath);
setPath(stickyNavItems ? stickyNavItems[0].defaultPath : "");
},[]);
console.log("path: ", path);
console.log("sessionData: ", sessionData);
return (
<Fragment>
{stickyNavItems && (
<nav class="StickyNav">
<ul class="StickyNav-items">
{stickyNavItems.map((item) => {
return (
<li class={`StickyNav-item${item.href == path ? " StickyNav-item--active" : ""}${item.pretext ? " StickyNav-item--pretext" : ""}`}>
<Link href={item.href} onClick={() => setPath(item.href)}>
{item.pretext && <span class="StickyNav-pretext">{item.pretext}</span>}
{item.text}
</Link>
</li>
);
})}
</ul>
</nav>
)}
</Fragment>
);
};
I guess you need to move stickyNavItems to state and update it using useEffect after sessionData is updated.
const [stickyNavItems, setNavItems] = useState(sessionData && sessionToken ? stickyNavItemsByRole(sessionData.user) : null);
useEffect(() => {
setNavItems(sessionData && sessionToken ? stickyNavItemsByRole(sessionData.user) : null)
}, [sessionData, sessionToken])
Related
Hi I have a parent component which contains a bunch child components which are checkboxes.
parent component is something like this inside:
const [items, setItems] = useState([
{
id:1,
selected: false,
},
{
id:2,
selected: false,
}
]);
const changeSelected = (id) =>
{
items.forEach((item)=>
{
if (item.id === id)
{
item.selected = !item.selected;
}
else{
item.selected = false;
}
})
}
return(
<div>
{items.map((item)=>{
<Child item={item} changeSelected={changeSelected}/>
})}
</div>
)
and in the child component, it has something like this inside:
return(
<div>
<input type="checkbox" checked={props.item.selected} onChange={()=>{props.changeSelected(props.item.id)}} />
</div>
)
I know partially this isnt working is because useState is async but I dont know what to do to make it work, or if I should try a different approach? Thank you
You can refactor your function to this:
const changeSelected = (id) => {
setItems(prev => ([...prev, {id, selected: !prev.filter(x => x.id === id)[0].selected}]))
}
Obviously I forgot to setItem according to the two answers I received. But I think the right way to do it is to make a deep copy of my existing items, and setItems again. For my future reference, here is what I have now working (an example):
let newItems = [...items];
newItems.forEach((item)=>
{
if (item.id === id)
{
item.selected = !item.selected;
}
else{
item.selected = false;
}
})
setProducts(newItems);
You are not updating the state anywhere. You should make a copy of the array and then change the object you want to:
const changeSelected = (id) =>
{ let newItems = [];
items.forEach((item)=>
{
if (item.id === id)
{
newItems.push({ id : item.id , selected : item.selected });
}
else{
newItems.push({ id : item.id , selected : false });
}
})
setItems(newItems);
}
Call setItems to set it.
Note:
Not related to the question but you should use unique keys when iterating over list.
{items.map((item)=>{
<Child key={item.id} item={item} changeSelected={changeSelected}/>
})}
I'm following this tutorial https://www.howtographql.com/react-apollo/0-introduction/ and I discovered an issue.
I have a component named LinkLists which displays a list of links and it's paginated.
import React, { useEffect } from "react";
import Link from "./Link";
import { useQuery } from "#apollo/client";
import { useNavigate } from "react-router-dom";
import { LINKS_PER_PAGE } from "../utilities/constants";
import FEED_QUERY from "../graphql/queries/feedLinks";
import NEW_LINKS_SUBSCRIPTION from "../graphql/subscriptions/newLinks";
import NEW_VOTES_SUBSCRIPTION from "../graphql/subscriptions/newVotes";
import getQueryVariables from "../utilities/getQueryVariables";
import getLinksToRender from "../utilities/getLinksToRender";
const LinkList = () => {
const navigate = useNavigate();
const isNewPage = window.location.pathname.includes("new");
const pageIndexParams = window.location.pathname.split("/");
const page = parseInt(pageIndexParams[pageIndexParams.length - 1]);
const pageIndex = page ? (page - 1) * LINKS_PER_PAGE : 0;
const { data, loading, error} = useQuery(FEED_QUERY, {
variables: getQueryVariables(isNewPage, page),
});
return (
<>
{loading && <p>Loading...</p>}
{error && <pre>{JSON.stringify(error, null, 2)}</pre>}
{data && (
<>
{getLinksToRender(isNewPage, data).map((link, index) => (
<Link key={link.id} link={link} index={index + pageIndex} />
))}
{isNewPage && (
<div className="flex ml4 mv3 gray">
<div
className="pointer mr2"
onClick={() => {
if (page > 1) {
navigate(`/new/${page - 1}`);
}
}}
>
Previous
</div>
<div
className="pointer"
onClick={() => {
if (page < data.feed.count / LINKS_PER_PAGE) {
const nextPage = page + 1;
navigate(`/new/${nextPage}`);
}
}}
>
Next
</div>
</div>
)}
</>
)}
</>
);
};
export default LinkList;
When next is clicked, it navigates to the next page and renders the next set of links correctly. But after the links are loaded and I try to go to a previous page with new variables, the data is not being updated. I get the recent data but on a different page which is supposed to render older data. I think it has to do something with the cache but I'm not really sure. I'm still learning about Apollo Client and I'm not sure what is going wrong especially that I followed a tutorial.
I'm working on a personal project with redux. My mapStateToProps function seems to me properly written. but when I try to use it to send an object to my store nothing works.
Here's my function:
const mapDispatchToProps = dispatch => {
return {
addOrder: (item) => {
dispatch(addOrder(item));
}
}
}
<div className="recordOrder">
<button onclick={() => this.props.addOrder(this.state)}>Enregistrer et lancer la commande</button>
</div>
And my reducer:
const initialState = {
orderList : []
}
console.log(initialState);
export default function rootReducer ( state= initialState, action){
const orderList = [...state.orderList];
let position
switch (action.type){
case ADD_ORDER:
return {
orderList : [...state.orderList, action.payload]
};
case DELETE_ORDER:
position = orderList.indexOf(action.payload)
orderList.splice(position, 1)
return {
orderList
}
default:
return state;
}
console.log(state)
}
My entire component as requested:
import React, { Component } from 'react';
import { NavItem } from 'react-bootstrap';
import menu from './menu';
import { connect } from 'react-redux';
import { addOrder} from '../action'
class getOrder extends Component {
state = {
number: `CMD-${Date.now()}`,
order:[],
total: 0 ,
menu:menu,
isPaid: false
}
addItem = (index) => {
const order = [...this.state.order];
const menu = [...this.state.menu];
let total = this.state.total;
const pizza = menu[index];
console.log(pizza);
let ind = order.findIndex((item) =>
item.article == pizza.name
)
if (ind === -1){
order.push({article: pizza.name, price: pizza.price, volume:1})
total = total + order[order.length-1].price
} else if (ind != -1){
order[ind].volume++
total = total + order[ind].price
}
this.setState({
order:order,
total:total
})
console.log("youpiii");
console.log(this.state.total);
console.log(this.state.order);
}
render() {
const menuDisplay= menu.map( (item) => {
return (
<div>
<img onClick={() => this.addItem(item.number)} src={`${process.env.PUBLIC_URL}${item.picture}`} alt="picture" />
<div className="tagPrice">
<p>{item.name}</p>
<p>{item.price} €</p>
</div>
</div>
)
});
const currentOrder = [...this.state.order]
const orderDisplay = currentOrder.map((item) => {
let price = item.price*item.volume;
console.log(price);
return (
<div>
<h1>{item.volume} × {item.article}</h1>
<p>{price} €</p>
</div>
)
} );
return (
<div className="takeOrder">
<div className="orderban">
<h1>Pizza Reflex</h1>
</div>
<div>
<div className="menuDisplay">
{menuDisplay}
</div>
<div className="orderBoard">
<h1>Détail de la commande N°{this.state.number}</h1>
{orderDisplay}
<div className="total">
<h2>Soit un total de {this.state.total} € </h2>
</div>
<div className="recordOrder">
<button onclick={() => this.props.addOrder(this.state)}>Enregistrer et lancer la commande</button>
</div>
</div>
</div>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return {
addOrder: (item) => {
dispatch(addOrder(item));
}
}
}
export default connect ( mapDispatchToProps) (getOrder);
Can someone tell me what I've missed ?
Thanks for your help !
What you are missing is more of your code it can not be solved with what you have.
In more details what I need is the this.state , combinedReducer
The easiest fix you can do now is changing yow mapDispatchToProps works better if it is an obj
const mapStateToProps = (state) => {
return {
// here you specified the properties you want to pass yow component fom the state
}
};
const mapDispatchToProps = {action1, action2};
export default connect ( mapDispatchToProps) (getOrder);
connectreceives two params mapStateToProps and mapDispatchToProps,
mapDispatchToProps is optional, but mapStateToProps is mandatory, there for you need to specified, if your are not going to pass anything you need to pass a null value
export default connect (null, mapDispatchToProps) (getOrder);
also avoid exporting components without a name
example
function MyButton () {}
const MyButtonConnect = connect(state, dispatch)(MyButton);
export default MyButtonConnect
This is my Lists component. I'm trying to map through the lists array but it shows an error message of "Cannot read property map of undefined"
import { Link } from 'react-router-dom'
import axios from 'axios'
import apiUrl from './../../apiConfig'
const Lists = (props) => {
const [lists, setLists] = useState([])
useEffect(() => {
axios({
url: `${apiUrl}/lists`,
method: 'GET',
headers: {
'Authorization': `Token token=${props.user.token}`
}
})
.then(res => setLists(res.data.lists))
.catch(console.error)
}, [])
// const listsJsx = lists.map(list => (
// <li key={list._id}>
// <Link to={`/lists/${list._id}`}>{list.name}</Link>
// </li>
// ))
return (
<div>
<h4>Lists</h4>
<ul>
{lists && lists.length > 0
? lists.map(list => {
return <div key={list._id}><Link to={`/lists/${list._id}`}>{list.name}</Link></div>
})
: 'Loading...'}
</ul>
</div>
)
}
export default Lists
That is because res.data.lists is undefined. You could try something like this:
.then(res => setLists(res.data.lists ? res.data.lists || []))
Try to test click event to increase a value in the redux state. But the counter value is always 0.
Counter render
render() {
const { counter, label, isSaving, isLoading, error } = this.props
return <form>
<legend>{label}</legend>
<pre>{JSON.stringify({ counter, isSaving, isLoading }, null, 2)}</pre>
<button ref='increment' onClick={this._onClickIncrement}>click me!</button>
<button ref='save' disabled={isSaving} onClick={this._onClickSave}>{isSaving ? 'saving...' : 'save'}</button>
<button ref='load' disabled={isLoading} onClick={this._onClickLoad}>{isLoading ? 'loading...' : 'load'}</button>
{error ? <div className='error'>{error}</div> : null}
</form>
}
Jest Test
let container, store
beforeEach(() => {
store = mockStore(initialState)
container = shallow(<Counter label='a counter!' store={store} />)
})
it('+++ check Props after increments counter', () => {
const mockClick = jasmine.createSpy('click');
expect(container.find('button').at(0).type()).toEqual('button');
const increment = container.find('button').at(0)
container.find('button').at(0).simulate('click')
container.find('button').at(0).simulate('click')
container.find('button').at(0).simulate('click')
const pre = container.find('pre');
// const pre = TestUtils.findRenderedDOMComponentWithTag(counter, 'pre')
console.log(container.props().counter);
// expect(JSON.parse(counter.find('pre').first.text()).counter.value).toEqual(3)
})